﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using l1cs.server.server.utils;
using System.Xml;
using System.Collections;
using l1cs.server.model.npc.action;
using System.Text.RegularExpressions;

namespace l1cs.server.model
{
    public class L1BossCycle
    {
        private static L1BossCycle _instance;

        public static L1BossCycle getInstance()
        {
            if ( _instance == null )
            {
                _instance = new L1BossCycle();
            }
            return _instance;
        }

        ////@XmlAttribute(name = "Name")
        //private String _name;
        ////@XmlElement(name = "Base")
        //private Base _base;

        ////@XmlAccessorType(XmlAccessType.FIELD)
        //private static class Base {
        //    //@XmlAttribute(name = "Date")
        //    private String _date;
        //    //@XmlAttribute(name = "Time")
        //    private String _time;

        //    public String getDate() {
        //        return _date;
        //    }

        //    public void setDate(String date) {
        //        this._date = date;
        //    }

        //    public String getTime() {
        //        return _time;
        //    }

        //    public void setTime(String time) {
        //        this._time = time;
        //    }
        //}

        ////@XmlElement(name = "Cycle")
        //private Cycle _cycle;

        ////@XmlAccessorType(XmlAccessType.FIELD)
        //private static class Cycle {
        //    //@XmlAttribute(name = "Period")
        //    private String _period;
        //    //@XmlAttribute(name = "Start")
        //    private String _start;
        //    //@XmlAttribute(name = "End")
        //    private String _end;

        //    public String getPeriod() {
        //        return _period;
        //    }

        //    public void setPeriod(String period) {
        //        this._period = period;
        //    }

        //    public String getStart() {
        //        return _start;
        //    }

        //    public void setStart(String start) {
        //        _start = start;
        //    }

        //    public String getEnd() {
        //        return _end;
        //    }

        //    public void setEnd(String end) {
        //        _end = end;
        //    }
        //}

        private static Random _rnd = new Random();
        //private Calendar _baseDate;
        //private int _period; // 分換算
        //private int _periodDay;
        //private int _periodHour;
        //private int _periodMinute;

        //private int _startTime; // 分換算
        //private int _endTime; // 分換算
        private const string _sdfYmd = "yyyy/MM/dd";
        //private static SimpleDateFormat _sdfTime = new SimpleDateFormat("HH:mm");
        //private static SimpleDateFormat _sdf = new SimpleDateFormat(			"yyyy/MM/dd HH:mm");
        private DateTime _initDate = DateTime.Now;
        private const String _initTime = "0:00";
        private DateTime START_UP = DateTime.Now;

        public DateTime init( BossCycle cycle )
        {
            // 基準日時の設定
            Base l1base = cycle.getBase();
            // 基準がなければ、現在日付の0:00基準
            if ( l1base == null )
            {
                cycle.setBase( new Base() );
                cycle.getBase().setDate( _initDate.ToString( _sdfYmd ) );
                cycle.getBase().setTime( _initTime );
                l1base = cycle.getBase();
            }
            else
            {
                DateTime dt;
                if ( DateTime.TryParse( l1base.getDate(), out dt ) == false )
                {
                    l1base.setDate( _initDate.ToString( _sdfYmd ) );
                }
                if ( DateTime.TryParse( l1base.getTime(), out dt ) == false )
                {
                    l1base.setTime( _initTime );
                }
            }
            // 基準日時を決定
            DateTime baseCal;
            DateTime.TryParse( l1base.getDate() + " " + l1base.getTime(), out baseCal );

            // 出現周期の初期化,チェック
            Cycle spawn = cycle.getCycle();
            if ( spawn == null || spawn.getPeriod() == null )
            {
                throw new Exception( "CycleのPeriodは必須" );
            }

            String period = spawn.getPeriod();
            int _periodDay = getTimeParse( period, "d" );
            int _periodHour = getTimeParse( period, "h" );
            int _periodMinute = getTimeParse( period, "m" );

            String start = spawn.getStart();
            int sDay = getTimeParse( start, "d" );
            int sHour = getTimeParse( start, "h" );
            int sMinute = getTimeParse( start, "m" );
            String end = spawn.getEnd();
            int eDay = getTimeParse( end, "d" );
            int eHour = getTimeParse( end, "h" );
            int eMinute = getTimeParse( end, "m" );

            // 分換算
            int _period = ( _periodDay * 24 * 60 ) + ( _periodHour * 60 ) + _periodMinute;
            int _startTime = ( sDay * 24 * 60 ) + ( sHour * 60 ) + sMinute;
            int _endTime = ( eDay * 24 * 60 ) + ( eHour * 60 ) + eMinute;
            if ( _period <= 0 )
            {
                throw new Exception( "must be Period > 0" );
            }
            // start補正
            if ( _startTime < 0 || _period < _startTime )
            { // 補正
                _startTime = 0;
            }
            // end補正
            if ( _endTime < 0 || _period < _endTime || end == null )
            { // 補正
                _endTime = _period;
            }
            if ( _startTime > _endTime )
            {
                _startTime = _endTime;
            }
            // start,endの相関補正(最低でも1分の間をあける)
            // start==endという指定でも、出現時間が次の周期に被らないようにするため
            if ( _startTime == _endTime )
            {
                if ( _endTime == _period )
                {
                    _startTime--;
                }
                else
                {
                    _endTime++;
                }
            }

            // 最近の周期まで補正(再計算するときに厳密に算出するので、ここでは近くまで適当に補正するだけ)
            while ( !( baseCal < START_UP ) )
            {
                baseCal.AddMonths( _periodDay - baseCal.Month );
                baseCal.AddHours( _periodHour - baseCal.Hour );
                baseCal.AddMinutes( _periodMinute - baseCal.Minute );
            }
            return baseCal;
        }

        ///*
        // * 指定日時を含む周期(の開始時間)を返す
        // * ex.周期が2時間の場合
        // *  target base 戻り値
        // *   4:59  7:00 3:00
        // *   5:00  7:00 5:00
        // *   5:01  7:00 5:00
        // *   6:00  7:00 5:00
        // *   6:59  7:00 5:00
        // *   7:00  7:00 7:00
        // *   7:01  7:00 7:00
        // *   9:00  7:00 9:00
        // *   9:01  7:00 9:00
        // */
        //private Calendar getBaseCycleOnTarget(Calendar target) {
        //    // 基準日時取得
        //    Calendar base = (Calendar) _baseDate.clone();
        //    if (target.after(base)) {
        //        // target <= baseとなるまで繰り返す
        //        while (target.after(base)) {
        //            base.add(Calendar.DAY_OF_MONTH, _periodDay);
        //            base.add(Calendar.HOUR_OF_DAY, _periodHour);
        //            base.add(Calendar.MINUTE, _periodMinute);
        //        }
        //    }
        //    if (target.before(base)) {
        //        while (target.before(base)) {
        //            base.add(Calendar.DAY_OF_MONTH, -_periodDay);
        //            base.add(Calendar.HOUR_OF_DAY, -_periodHour);
        //            base.add(Calendar.MINUTE, -_periodMinute);
        //        }
        //    }
        //    // 終了時間を算出してみて、過去の時刻ならボス時間が過ぎている→次の周期を返す。
        //    Calendar end = (Calendar) base.clone();
        //    end.add(Calendar.MINUTE, _endTime);
        //    if (end.before(target)) {
        //        base.add(Calendar.DAY_OF_MONTH, _periodDay);
        //        base.add(Calendar.HOUR_OF_DAY, _periodHour);
        //        base.add(Calendar.MINUTE, _periodMinute);
        //    }
        //    return base;
        //}

        ///**
        // * 指定日時を含む周期に対して、出現タイミングを算出する。
        // * @return 出現する時間
        // */
        //public Calendar calcSpawnTime(Calendar now) {
        //    // 基準日時取得
        //    Calendar base = getBaseCycleOnTarget(now);
        //    // 出現期間の計算
        //    base.add(Calendar.MINUTE, _startTime);
        //    // 出現時間の決定 start～end迄の間でランダムの秒
        //    int diff = (_endTime - _startTime) * 60;
        //    int random = diff > 0 ? _rnd.nextInt(diff) : 0;
        //    base.add(Calendar.SECOND, random);
        //    return base;
        //}

        ///**
        // * 指定日時を含む周期に対して、出現開始時間を算出する。
        // * @return 周期の出現開始時間
        // */
        //public Calendar getSpawnStartTime(Calendar now) {
        //    // 基準日時取得
        //    Calendar startDate = getBaseCycleOnTarget(now);
        //    // 出現期間の計算
        //    startDate.add(Calendar.MINUTE, _startTime);
        //    return startDate;
        //}

        ///**
        // * 指定日時を含む周期に対して、出現終了時間を算出する。
        // * @return 周期の出現終了時間
        // */
        //public Calendar getSpawnEndTime(Calendar now) {
        //    // 基準日時取得
        //    Calendar endDate = getBaseCycleOnTarget(now);
        //    // 出現期間の計算
        //    endDate.add(Calendar.MINUTE, _endTime);
        //    return endDate;
        //}

        ///**
        // * 指定日時を含む周期に対して、次の周期の出現タイミングを算出する。
        // * @return 次の周期の出現する時間
        // */
        //public Calendar nextSpawnTime(Calendar now) {
        //    // 基準日時取得
        //    Calendar next = (Calendar) now.clone();
        //    next.add(Calendar.DAY_OF_MONTH, _periodDay);
        //    next.add(Calendar.HOUR_OF_DAY, _periodHour);
        //    next.add(Calendar.MINUTE, _periodMinute);
        //    return calcSpawnTime(next);
        //}

        ///**
        // * 指定日時に対して、最近の出現開始時間を返却する。
        // * @return 最近の出現開始時間
        // */
        //public Calendar getLatestStartTime(Calendar now) {
        //    // 基準日時取得
        //    Calendar latestStart = getSpawnStartTime(now);
        //    if (!now.before(latestStart)) { // now >= latestStart
        //    } else {
        //        // now < latestStartなら1個前が最近の周期
        //        latestStart.add(Calendar.DAY_OF_MONTH, -_periodDay);
        //        latestStart.add(Calendar.HOUR_OF_DAY, -_periodHour);
        //        latestStart.add(Calendar.MINUTE, -_periodMinute);
        //    }

        //    return latestStart;
        //}

        private static int getTimeParse( String target, String search )
        {
            if ( target == null )
            {
                return 0;
            }
            int n = 0;
            //Matcher matcher = Pattern.compile( "\\d+" + search ).matcher( target );
            Match matcher = new Regex( "\\d+" + search ).Match( target );
            if ( matcher.Success )
            {
                String match = matcher.Value;
                n = Convert.ToInt32 ( match.Replace( search, "" ) );
            }
            return n;
        }

        //@XmlAccessorType(XmlAccessType.FIELD)
        //@XmlRootElement(name = "BossCycleList")
        //static class L1BossCycleList {
        //    @XmlElement(name = "BossCycle")
        //    private List<L1BossCycle> bossCycles;

        //    public List<L1BossCycle> getBossCycles() {
        //        return bossCycles;
        //    }

        //    public void setBossCycles(List<L1BossCycle> bossCycles) {
        //        this.bossCycles = bossCycles;
        //    }
        //}

        private L1BossCycle()
        {
            PerformanceTimer timer = new PerformanceTimer();
            _log.info( "loading boss cycle..." );

            // BookOrder クラスをバインディングするコンテキストを生成
            //JAXBContext context = JAXBContext
            //        .newInstance(L1BossCycle.L1BossCycleList.class);

            //// XML -> POJO 変換を行うアンマーシャラを生成
            //Unmarshaller um = context.createUnmarshaller();

            //// XML -> POJO 変換
            //File file = new File("./data/xml/Cycle/BossCycle.xml");
            //L1BossCycleList bossList = (L1BossCycleList) um.unmarshal(file);
            // 「XmlReader」インスタンスの宣言(nullで初期化)
            XmlReader reader = null;
            // 特別なルールを設定する場合、「XmlReaderSettings」クラスを使用する。
            XmlReaderSettings settings = new XmlReaderSettings();
            // 検証は「整形式かどうか」の検査のみ
            settings.ConformanceLevel = ConformanceLevel.Document;

            // 「XmlReader」の作成
            reader = XmlReader.Create( "./data/xml/Cycle/BossCycle.xml", settings );

            BossCycle cycle = null;
            string name = null;
            string date = null;
            string time = null;
            string period = null;
            string start = null;
            while ( reader.Read() )
            {
                // 開始タグを発見した場合
                if ( reader.NodeType == XmlNodeType.Element )
                {
                    if ( string.Compare( reader.LocalName, "BossCycle", true ) == 0 )
                    {
                        Hashtable attribute = L1NpcXmlParser.getAllAttribute( reader );
                        name = (string)attribute["Name"];
                    }
                    if ( string.Compare( reader.LocalName, "Base", true ) == 0 )
                    {
                        Hashtable attribute = L1NpcXmlParser.getAllAttribute( reader );
                        date = (string)attribute["Date"];
                        time = (string)attribute["Time"];
                    }
                    if ( string.Compare( reader.LocalName, "Cycle", true ) == 0 )
                    {
                        Hashtable attribute = L1NpcXmlParser.getAllAttribute( reader );
                        period = (string)attribute["Period"];
                        start = (string)attribute["Start"];
                    }
                }
                else if ( reader.NodeType == XmlNodeType.EndElement &&
                          string.Compare( reader.LocalName, "BossCycle", true ) == 0 )
                {
                    cycle = new BossCycle( name, date, time, period, start );
                    cycle.setBaseTime( init( cycle ) );
                    _cycleMap.Add( cycle.getName(), cycle );
                }
            }
            //foreach (L1BossCycle cycle in bossList.getBossCycles()) {
            //    cycle.init();
            //    _cycleMap.put(cycle.getName(), cycle);
            //}

            //// userデータがあれば上書き
            string userFile = "./data/xml/Cycle/users/BossCycle.xml";
            if ( System.IO.File.Exists( userFile ) )
            {
                // 「XmlReader」の作成
                reader = XmlReader.Create( "./data/xml/Cycle/BossCycle.xml", settings );

                while ( reader.Read() )
                {
                    // 開始タグを発見した場合
                    if ( reader.NodeType == XmlNodeType.Element )
                    {
                        if ( string.Compare( reader.LocalName, "BossCycle", true ) == 0 )
                        {
                            Hashtable attribute = L1NpcXmlParser.getAllAttribute( reader );
                            name = (string)attribute["Name"];
                        }
                        if ( string.Compare( reader.LocalName, "Base", true ) == 0 )
                        {
                            Hashtable attribute = L1NpcXmlParser.getAllAttribute( reader );
                            date = (string)attribute["Date"];
                            time = (string)attribute["Time"];
                        }
                        if ( string.Compare( reader.LocalName, "Cycle", true ) == 0 )
                        {
                            Hashtable attribute = L1NpcXmlParser.getAllAttribute( reader );
                            period = (string)attribute["Period"];
                            start = (string)attribute["Start"];
                        }
                    }
                    else if ( reader.NodeType == XmlNodeType.EndElement &&
                              string.Compare( reader.LocalName, "BossCycle", true ) == 0 )
                    {
                        cycle = new BossCycle( name, date, time, period, start );
                        cycle.setBaseTime( init( cycle ) );
                        _cycleMap[cycle.getName()] = cycle;
                    }
                }
                //    foreach (L1BossCycle cycle in bossList.getBossCycles()) {
                //        cycle.init();
                //        _cycleMap.put(cycle.getName(), cycle);
                //    }
            }
            //// spawnlist_bossから読み込んで配置
            //BossSpawnTable.fillSpawnTable();

            _log.info( "OK! " + timer.get() + "ms" );
        }

        ///**
        // * 周期名と指定日時に対する出現期間、出現時間をコンソール出力
        // * @param now 周期を出力する日時
        // */
        //public void showData(Calendar now) {
        //    System.out.println("[Type]" + getName());
        //    System.out.print("  [出現期間]");
        //    System.out.print(_sdf.format(getSpawnStartTime(now).getTime()) + " - ");
        //    System.out.println(_sdf.format(getSpawnEndTime(now).getTime()));
        //}

        private Dictionary<String, BossCycle> _cycleMap = new Dictionary<String, BossCycle>();

        //public static L1BossCycle getBossCycle(String type) {
        //    return _cycleMap.get(type);
        //}

        //public String getName() {
        //    return _name;
        //}

        //public void setName(String name) {
        //    this._name = name;
        //}

        //public Base getBase() {
        //    return _base;
        //}

        //public void setBase(Base base) {
        //    this._base = base;
        //}

        //public Cycle getCycle() {
        //    return _cycle;
        //}

        //public void setCycle(Cycle cycle) {
        //    this._cycle = cycle;
        //}

        private static Logger _log = Logger.getLogger( typeof( L1BossCycle ).FullName );
    }

    public class BossCycle
    {
        private String _name;

        private Base _base;

        private Cycle _cycle;

        private DateTime _baseTime;

        public BossCycle( string name, string date, string time, string period, string start )
        {
            _name = name;
            _base = new Base( date, time );
            _cycle = new Cycle( period, start );
        }

        public string getName()
        {
            return _name;
        }

        public Base getBase()
        {
            return _base;
        }

        public void setBase( Base l1base )
        {
            _base = l1base;
        }

        public Cycle getCycle()
        {
            return _cycle;
        }

        public void setCycle( Cycle cycle )
        {
            _cycle = cycle;
        }

        public void setBaseTime( DateTime baseTime )
        {
            _baseTime = baseTime;
        }
    }

    public class Base
    {
        private String _date;

        private String _time;

        public Base() { }
        public Base( string date, string time )
        {
            _date = date;
            _time = time;
        }

        public String getDate()
        {
            return _date;
        }

        public void setDate( String date )
        {
            this._date = date;
        }

        public String getTime()
        {
            return _time;
        }

        public void setTime( String time )
        {
            this._time = time;
        }
    }

    public class Cycle
    {
        private String _period;

        private String _start;

        private String _end;

        public Cycle() { }

        public Cycle( string period, string start )
        {
            _period = period;
            _start = start;
        }

        public String getPeriod()
        {
            return _period;
        }

        public void setPeriod( String period )
        {
            this._period = period;
        }

        public String getStart()
        {
            return _start;
        }

        public void setStart( String start )
        {
            _start = start;
        }

        public String getEnd()
        {
            return _end;
        }

        public void setEnd( String end )
        {
            _end = end;
        }
    }
}
